package com.d7024e.firstTest;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import com.ekahau.engine.sdk.Device;
import com.ekahau.engine.sdk.DeviceLocation;
import com.ekahau.engine.sdk.EngineException;
import com.ekahau.engine.sdk.IDeviceLocationListener;
import com.ekahau.engine.sdk.Model;
import com.ekahau.engine.sdk.ModelMap;
import com.ekahau.engine.sdk.PositioningEngine;
import com.ekahau.engine.sdk.event.AbstractEngineEventListener;
import com.ekahau.engine.sdk.event.EngineEvent;
import com.ekahau.engine.sdk.event.LocationEstimate;

/**
 * Sample command line application for the Java SDK.
 * 
 * @author Ekahau, Inc.
 */
public class CommandLineApplication {
	
	static {
		// Only log warnings
		System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
		System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true");
		System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", "warn");
	}
	
	// Default values
	private static final String DEFAULT_HOSTNAME = "130.240.233.27";
	private static final int DEFAULT_PORT = 8550;
	private static final String DEFAULT_USERNAME = "mdcs";
	private static final String DEFAULT_PASSWORD = "mdcs";
	
	// Commands
	private static final int COMMAND_LIST = 1;
	private static final int COMMAND_POS = 2;
	private static final int COMMAND_PROPERTIES = 3;
	private static final int COMMAND_BUZZERT201 = 4;
	private static final int COMMAND_BUZZERT301 = 5;
	private static final int COMMAND_STREAM = 6;
	private static final int COMMAND_MAP = 7;
	private static final int COMMAND_EVENT = 8;

	// Configuration data object
	static class Config {
		String hostname = DEFAULT_HOSTNAME;
		int port = DEFAULT_PORT;
		String username = DEFAULT_USERNAME;
		String password = DEFAULT_PASSWORD;
		int command;
		String macId;		// mac or mapid or ruleid
	}
	
	// Connection to engine
	private static PositioningEngine mEngine;
	
	// Lists all devices in the system, returns status code,
	// throws EngineException if error
	private static int listAllDevices() throws EngineException {
		List devices = mEngine.findDevices();
		Iterator it = devices.iterator();
		while (it.hasNext()) {
			DeviceLocation d = (DeviceLocation) it.next();
			String mac = d.getMAC();
			System.out.println("Device MAC: " + mac);
		}
		return 0;
	}
		
	// Prints device position, returns status code,
	// throws EngineException if error
	private static int printDevicePosition(String mac) throws EngineException {
		List devices = mEngine.findDevices(new String[] { mac });
		if (devices.isEmpty()) {
			System.err.println("error: no such device");
			return 1;
		}
		DeviceLocation d = (DeviceLocation) devices.get(0);
		Integer posx = d.getX();
		Integer posy = d.getY();
		if (posx == null || posy == null) {
			System.out.println("No position for device");
			return 1;
		}
		System.out.println("MAC: " + mac + ", posx: " + posx + ", posy: " + posy);
		return 0;
	}
		
	// Prints all device properties, returns status code,
	// throws EngineException if fails
	private static int printDeviceProperties(String mac) throws EngineException {
		List devices = mEngine.findDevices(new String[] { mac });
		if (devices.isEmpty()) {
			System.err.println("error: no such device");
			return 1;
		}
		DeviceLocation d = (DeviceLocation) devices.get(0);
		System.out.println(d.toString());
		return 0;
	}

	// Sends buzzer command to a T201 device, returns status code,
	// throws EngineException if fails
	private static int sendBuzzerCommandT201(String mac) throws EngineException {
		mEngine.sendCommand(mac, "buzzer duration 2");
		return 0;
	}
		
	// Sends buzzer command to a T301 device, returns status code,
	// throws EngineException if fails
	private static int sendBuzzerCommandT301(String mac) throws EngineException {
		mEngine.sendCommand(mac, "mmm 0");
		mEngine.sendCommand(mac, "mmt 1");
		return 0;
	}
		
	// Gets map image, throws EngineException or return null if fails
	private static byte[] getMapImage(String mapId)
			throws EngineException {
		int id;
		try {
			id = Integer.parseInt(mapId);
		} catch (NumberFormatException ex) {
			System.err.println("error: invalid id: " + mapId);
			return null;
		}
		Model model = mEngine.getActiveModel();
		if (model == null) {
			System.err.println("error: no active model");
			return null;
		}
		ModelMap map = model.getMapById(id);
		if (map == null) {
			System.err.println("error: no such map");
			return null;
		}
		return map.getPngMapImage();
	}
	
	// Listen to new locations from stream
	static class StreamListener extends AbstractEngineEventListener 
			implements IDeviceLocationListener {
		private boolean isClosed = false;
		
		public void connectionClosed(Exception reason) {
			System.err.println("Connection closed by server: " + reason);
			// Notify waiters that connection was closed
			synchronized (this) {
				this.isClosed = true;
				notifyAll();
			}
		}
		
		// Wait until connection is closed or thread is interrupted
		synchronized void waitUntilClosed() throws InterruptedException {
			while (!isClosed) {
				wait();
			}
		}

		public void newDeviceLocation(DeviceLocation pDeviceLocation) {
			// Print all properties from response
			System.out.println(pDeviceLocation.toString());
		}

		public void newEvent(EngineEvent pEvent, Device pDevice, LocationEstimate pLocation) {
			// Print all properties from response
			String eventDesc = pEvent.toString();
			if (pDevice != null)
				eventDesc += " --- device: " + pDevice.toString();
			if (pLocation != null)
				eventDesc += " --- location: " + pLocation.toString();
			
			System.out.println(eventDesc);
		}
	}
	
	// Print usage to stdout
	private static void usage() {
		System.out.println("Usage: java CommandLineApplication [options] command");
		System.out.println("       java CommandLineApplication [options] pos|properties|buzzer|buzzert201 MAC");
		System.out.println("       java CommandLineApplication [options] stream MAC|ALL");
		System.out.println("       java CommandLineApplication [options] map MAPID");
		System.out.println("       java CommandLineApplication [options] event RULEID");
		System.out.println();
		System.out.println("Sample application for the Ekahau Positioning Engine Java SDK.");
		System.out.println();
		System.out.println("Commands:");
		System.out.println("  list            list all devices");
		System.out.println("  pos MAC         get position of device MAC");
		System.out.println("  properties MAC  get all properties for device MAC");
		System.out.println("  buzzert201 MAC  play buzzer of a T201-device MAC for 2 seconds");
		System.out.println("  buzzer MAC      play buzzer of a T301-device MAC once");
		System.out.println("  stream MAC/ALL  open position stream for device MAC (or ALL devices)");
		System.out.println("  map MAPID       store map MAPID of active model in PNG format to current");
		System.out.println("                  directory");
		System.out.println("  event RULEID    open event stream for rule RULEID");
		System.out.println();
		System.out.println("Options:");
		System.out.println("  -h HOSTNAME     engine server hostname (default: \"" +
				DEFAULT_HOSTNAME + "\")");
		System.out.println("  -p PORT         engine server port (default: \"" +
				DEFAULT_PORT + "\")");
		System.out.println("  -u USERNAME     engine username (default: \"" +
				DEFAULT_USERNAME + "\")");
		System.out.println("  -w PASSWORD     password for the user (default: \"" +
				DEFAULT_PASSWORD + "\")");
		System.out.println("  --help          display this help and exit");
	}
	
	// Parse arguments to a Config object, return null if invalid args
	private static Config parseArgs(String[] args) {
		Config ret = new Config();
		// parse options
		int i;
		for (i = 0; i < args.length; ++i) {
			String opt = args[i];
			if (!opt.startsWith("-"))
				break;
			if (opt.equals("-h")) {
				if (i < args.length - 1) {
					ret.hostname = args[++i];
				} else {
					System.err.println(opt + " needs an argument. Try --help for usage.");
					return null;
				}
			} else if (opt.equals("-p")) {
				if (i < args.length - 1) {
					String portStr = args[++i];
					try {
						ret.port = Integer.parseInt(portStr);
					} catch (NumberFormatException e) {
						System.err.println(portStr + " is not valid port.");
						return null;
					}
				} else {
					System.err.println(opt + " needs an argument. Try --help for usage.");
					return null;
				}
			} else if (opt.equals("-u")) {
				if (i < args.length - 1) {
					ret.username = args[++i];
				} else {
					System.err.println(opt + " needs an argument. Try --help for usage.");
					return null;
				}
			} else if (opt.equals("-w")) {
				if (i < args.length - 1) {
					ret.password = args[++i];
				} else {
					System.err.println(opt + " needs an argument. Try --help for usage.");
					return null;
				}
			} else {
				System.err.println("Unknown option '" + opt + "'. Try --help for usage.");
			}
		}
		
		// parse command
		if (i == args.length) {
			System.err.println("Command missing. Try --help for usage.");
			return null;
		}
		
		String cmd = args[i];
		if (cmd.equals("list")) {
			ret.command = COMMAND_LIST;
		} else if (cmd.equals("pos")) {
			ret.command = COMMAND_POS;
			if (i < args.length - 1) {
				ret.macId = args[i + 1];
			} else {
				System.err.println("'" + cmd + "' needs an argument. Try --help for usage.");
				return null;
			}
		} else if (cmd.equals("properties")) {
			ret.command = COMMAND_PROPERTIES;
			if (i < args.length - 1) {
				ret.macId = args[i + 1];
			} else {
				System.err.println("'" + cmd + "' needs an argument. Try --help for usage.");
				return null;
			}
		} else if (cmd.equals("buzzert201")) {
			ret.command = COMMAND_BUZZERT201;
			if (i < args.length - 1) {
				ret.macId = args[i + 1];
			} else {
				System.err.println("'" + cmd + "' needs an argument. Try --help for usage.");
				return null;
			}
		} else if (cmd.equals("buzzer")) {
			ret.command = COMMAND_BUZZERT301;
			if (i < args.length - 1) {
				ret.macId = args[i + 1];
			} else {
				System.err.println("'" + cmd + "' needs an argument. Try --help for usage.");
				return null;
			}
		} else if (cmd.equals("stream")) {
			ret.command = COMMAND_STREAM;
			if (i < args.length - 1) {
				ret.macId = args[i + 1];
			} else {
				System.err.println("'" + cmd + "' needs an argument. Try --help for usage.");
				return null;
			}
		} else if (cmd.equals("map")) {
			ret.command = COMMAND_MAP;
			if (i < args.length - 1) {
				ret.macId = args[i + 1];
			} else {
				System.err.println("'" + cmd + "' needs an argument. Try --help for usage.");
				return null;
			}
		} else if (cmd.equals("event")) {
			ret.command = COMMAND_EVENT;
			if (i < args.length - 1) {
				ret.macId = args[i + 1];
			} else {
				System.err.println("'" + cmd + "' needs an argument. Try --help for usage.");
				return null;
			}
		} else {
			System.err.println("Unknown command '" + cmd + "'. Try --help for usage.");
			return null;
		}
		
		return ret;
	}
	
	// create unique file 'map<mapid>_n.png'
	private static File createDiskFile(String mapid) throws IOException {
		// Try without suffix first
		File file = new File("map" + mapid + ".png");
		boolean ok = file.createNewFile();
		if (ok) {
			return file;
		}
		// Then iterate some values of 'n'
		for (int i = 1; i < 100; ++i) {
			file = new File("map" + mapid + "_" + i + ".png");
			ok = file.createNewFile();
			if (ok) {
				return file;
			}
		}
		// Give up
		return null;
	}
	
	// Try to save image to disk with a unique filename, return filename or null
	private static String saveImageToDisk(byte[] imgData, String mapid)
		throws IOException {
		File file = createDiskFile(mapid);
		if (file == null) {
			System.err.println("error: couldn't get filename");
			return null;
		}
		FileOutputStream os = null;
		
		try {
			os = new FileOutputStream(file);
			os.write(imgData);
			return file.getName();
		} finally {
			if (os != null) {
				try {
					os.close();
				} catch (IOException e) {
					// Ignored
				}
			}
		}
	}
	
	// Run command from configuration, return status
	private static int runCommand(Config cfg) {
		mEngine = new PositioningEngine(cfg.hostname, cfg.port,
				cfg.username, cfg.password);
		try {
			switch (cfg.command) {
			case COMMAND_LIST:
				return listAllDevices();
			case COMMAND_POS:
				return printDevicePosition(cfg.macId);
			case COMMAND_PROPERTIES:
				return printDeviceProperties(cfg.macId);
			case COMMAND_BUZZERT201:
				return sendBuzzerCommandT201(cfg.macId);
			case COMMAND_BUZZERT301:
				return sendBuzzerCommandT301(cfg.macId);
			case COMMAND_STREAM: {
				StreamListener l = new StreamListener();
				if (cfg.macId.equals("ALL")) {
					mEngine.addLocationListener(l);
				} else {
					mEngine.addLocationListener(l, new String[] { cfg.macId });
				}
				mEngine.startTracking();
				// Wait for stream connection to close
				try {
					l.waitUntilClosed();
				} catch (InterruptedException e) {
					// Ignored
				}
				return 0;
			}
			case COMMAND_MAP: {
				byte[] imgData = getMapImage(cfg.macId);
				if (imgData == null) {
					return 1;
				}
				// Save img to disk
				String filename = saveImageToDisk(imgData, cfg.macId);
				if (filename == null) {
					return 1;
				}
				System.out.println("Map saved to " + filename);
				return 0;
			}
			case COMMAND_EVENT: {
				StreamListener l = new StreamListener();
				long ruleId = Long.parseLong(cfg.macId);
				mEngine.addEventListener(l, new long[] { ruleId }, null, false);
				mEngine.startTracking();
				// Wait for stream connection to close
				try {
					l.waitUntilClosed();
				} catch (InterruptedException e) {
					// Ignored
				}
				return 0;
				
			}
			default:
				System.err.println("error: invalid command");
				return 1;
			}
		} catch (EngineException e) {
			e.printStackTrace(System.err);
			return 1;
		} catch (IOException e) {
			e.printStackTrace(System.err);
			return 1;
		}
	}
	
	/**
	 * Entry point for program.
	 * @param args Optional arguments.
	 */
	public static void main(String[] args) {
		if (args.length == 0) {
			usage();
			System.exit(1);
		}
		if (args[0].equals("--help")) {
			usage();
			System.exit(0);
		}
		
		Config cfg = parseArgs(args);
		if (cfg == null) {
			System.exit(1);
		}
		
		int ret = runCommand(cfg);
		System.exit(ret);
	}
}
